多会话管理与 mock 接口服务
单个聊天窗口只够简单场景。这节让 playground 的左侧列表真正可切换——多会话数据不再写死在页面里,而是由一个独立的 mock 接口服务提供。这个服务(dev-server)后来成长为整个课程项目的前后端联调基座。
为什么需要多会话
用户可能同时进行多个 AI 对话:一个写代码、一个翻译文档、一个分析数据。每个对话有独立的消息历史和上下文,不能混在一起。多会话管理的核心是一个会话列表 + 当前会话切换。
playground 此前的问题很典型:左侧列表是静态假数据,点击后中间的消息内容不切换——UI 有了,数据链路没通。要打通它,缺的不是组件,而是一个能按会话维度提供数据的服务端。
两步走策略
功能复杂时分两步下指令,比一锅出可靠得多:
- 第一步:多消息切换。要求 playground 页面支持点击左侧列表切换中间消息内容。关键约束:"playground 现有数据先不要动,直接新增一个消息进行示例集成,之后再改造 playground 页面"——先增量验证,不动存量,出问题随时可退
- 第二步:独立 mock 服务包。因为有 stream 类型数据(SSE 流式),mock 要能模拟真实服务端 API 的响应行为,所以单独建包(
packages/dev-server),而不是在页面里写死
会话类型设计
会话的数据结构(dev-server 的 DTO 形态):
// 列表项:不含消息体,列表接口保持轻量
interface ConversationSummary {
id: string
title: string
description: string
updatedAt: string
messageCount: number
}
// 详情:按需拉取消息
interface ConversationDetail extends ConversationSummary {
messages: ChatMessageDTO[]
}
typescript
消息 DTO 支持携带 thinking(思考过程)与 toolCalls(工具调用)扩展字段——多会话不只是"多条文本历史",上一章做的思考展示、工具调用可视化都要能在会话间切换时正常回放。
前端组件族(真实仓库 conversation/ 目录):Conversation.vue 容器、ConversationContent.vue 消息区、ConversationEmpty.vue 空态、ConversationScrollBtn.vue 回到底部按钮。侧边栏布局沿用第11章容器查询:桌面端常驻,≤640px 时抽屉化。
设计注记:早期方案是
useConversationscomposable + localStorage 持久化(自动标题取第一条用户消息前 20 字符、删除当前会话自动切到第一个)。真实落地转向了服务端数据源——会话存在 dev-server 内存里(种子数据 + 运行期增改),前端经 SSE 适配器拉取。localStorage 方案没有错,它适合"纯前端、无后端"的场景;一旦有服务端,会话归属、跨端同步都该归服务端管。
mock 服务选型:让 AI 带着调研推荐
mock 服务端用 Node.js 写有很多方案。不必先自己调研再下指令——把指令和调研要求写进同一次对话:
"创建 mock 服务端,不要用 Node.js 原生方案(不用原生 http 模块手写),先搜索业界主流方案,使用 NPM 上成熟的库来实现。"
模型调研后给出了三个候选及示例用法:
| 方案 | 定位 | 周下载量(课程录制时口径) |
|---|---|---|
| Hono | 多运行时 Web 框架,轻量高性能 | ~4000 万 |
| MSW | Mock Service Worker,拦截请求层 mock | ~1000 万+ |
| Fastify | Node.js 高性能框架 | ~900 万+ |
(下载量随时间与统计口径波动较大——Hono 的多运行时安装很大一部分不经过 npm 统计,Fastify/MSW 的 npm 口径 2026 年也在 2000-3000 万量级。选型看的是场景匹配,不是单一数字。)
最终选 Hono:路由 API 简洁、TypeScript 优先、零依赖体积小,且后续接真实 agent 网关(第15-16章)也是同一框架,mock 与真实服务的心智模型完全一致。MSW 的优势在"前端测试拦截请求",与"独立起一个服务进程"的目标不符。
dev-server 的真实结构
- 会话路由:内存 Map 存储(种子数据 + 运行期增改,重启还原——对 dev 服务恰好是优点,测试可各建独立 app 互不污染)。列表按 updatedAt 倒序,
toSummary剥掉 messages 只留计数;对话结束后appendExchange把用户消息和完整助手回写历史 - 流式剧本:
mock/scenarios.ts把每个演示场景(思考链、工具调用、错误、慢速、Markdown)编排成[chunk类型, 内容, 停顿ms]序列,编译阶段将 text/thinking 按 token 粒度展开(每 4 字符一块、间隔 45ms),tool_call/tool_result/done 是原子事件不拆分——这就是上一章"流式 Markdown 容错"需求的配套数据源 - 种子会话覆盖四类消息形态:纯文本问答、思维链、工具调用、富 Markdown,playground 切换会话时每类渲染链路都被验证
接口文档:选现成方案,不自建
mock 服务的接口需要可视化文档(像 Swagger 那样能看列表、能发测试请求)。给模型的指令是"在文档站顶部创建一个单独的接口文档页面,看看有没有比较方便的现成展示方案"。
模型给出自建与现成两条路,选了现成:dev-server 输出 OpenAPI 3.1 规范(GET /api/openapi.json),文档站用 Scalar(@scalar/api-reference standalone 构建)加载渲染。理由:
- OpenAPI JSON 是标准契约,不是给页面看的装饰——后续要做真实后端时,把这个文件直接交给大模型,无论 Java、Python、Go,都能据此生成对应的接口实现
- Scalar 自带 Test Request(页面上直接发请求看响应)、JSON/YAML 规范下载、多语言调用示例——自建这些是重复造轮子,且可靠性远不如成熟库
落地细节(真实仓库):文档站 VitePress 通过 alias 指到 @scalar/api-reference 的 standalone ESM 构建(免 React 依赖);Scalar 区域定高一屏、滚动收在容器内部,不与站内导航打架。mock 演示页顶部有连接状态提示条——dev-server 没启动时状态不是"已连接",先探活(/api/health)再渲染,给使用者明确的排障信号。
小结
- 多会话打通走两步:先增量集成切换(不动存量数据),再独立 mock 服务包
- 会话 DTO 分 Summary(列表轻量)/Detail(按需拉消息)两层,消息可携带 thinking/toolCalls 扩展
- 指令与调研合并一次对话:"不要 Node.js 原生方案,搜索主流方案用成熟库"——选型 Hono(与后续真实网关同框架)
- 流式剧本按 token 粒度编译回放,为流式容错渲染提供真实数据源
- 接口文档选 OpenAPI 3.1 + Scalar 现成方案:规范即契约,可跨语言生成后端,页面自带 Test Request 与规范下载
- dev-server 是课程项目的联调基座:mock 模式零依赖启动,后续接真实 agent(第15-17章)同进程演进
下一节实现国际化(i18n)——让组件库支持多语言。
↑